/**
 * 扩展axios
 * 对于 `get`、`delete`、`head`、`options`、`post`、`patch`、`put` 这些方法，都是对外提供的语法糖，内部都是通过调用 `request` 方法实现发送请求，
 * 只不过在调用之前对 `config` 做了一层合并处理
 * 
 * 拦截器
*/
import { AxiosRequestConfig, AxiosResponse, AxiosPromise, Method, ResolvedFn, RejectedFn } from '../types'
import dispatchRequest from './dispatchRequest'
import InterceptorManager from './InterceptorManager'
import mergeConfig from './meregeConfig';

// request、response是InterceptorManager实例对象
interface Interceptors {
    request:  InterceptorManager<AxiosRequestConfig>,   
    response: InterceptorManager<AxiosResponse>,
}

interface PromiseChain<T> {
    // resolved可能是ResolvedFn 或 dispatchRequest(发送ajax请求时候)
    resolved: ResolvedFn<T> | ((config: AxiosRequestConfig)=> AxiosPromise)
    rejected?: RejectedFn
}

export default class Axios {
    // request(config: AxiosRequestConfig): AxiosPromise {
    //     return dispatchRequest(config)
    // }
    
    // 拦截器
    interceptors: Interceptors;

    // 默认配置
    defaults: AxiosRequestConfig;

    constructor(initConfig: AxiosRequestConfig) {
        /**
         * 拦截器.
         * request  请求拦截器.
         * response 响应拦截器.
         */
        this.interceptors = {
            request: new InterceptorManager<AxiosRequestConfig>(),
            response: new InterceptorManager<AxiosResponse>(),
        }
         
        this.defaults = initConfig

    }

    // axios(url,[config])、axios(config)支持这两种方式
    request(url: any, config?: any): AxiosPromise {
        // axios(url,[config])
        if(typeof url === 'string') {
            config = !config ? {}: config;
            config.url = url;
        } else {
            // axios(config)
            config = url;
        }
        
        // 合并请求配置
        config = mergeConfig(this.defaults, config)

        // 定义调用链:  默认是send request
        // request intercepter2 => request intercepter1 => send request =>  response intercepter1 => response intercepter1
        const chain: PromiseChain<any>[] = [
            {  
                resolved: dispatchRequest,  
                rejected: undefined
            }
        ]
        
        // 添加请求拦截器：先插入的后执行
        // 注意： 这里的forEach是InterceptorManager类中自定义方法
        this.interceptors.request.forEach(interceptor => {
            chain.unshift(interceptor)
        })

        // 添加响应拦截器： 先插入先执行
        this.interceptors.response.forEach(interceptor => {
            chain.push(interceptor)
        })
        
        // promise链依次执行: 最开始参数是config（因为连从请求拦截器开始执行，请求拦截器修改的是config）
        let promise = Promise.resolve(config)
        while(chain.length) {
            const { resolved, rejected } = chain.shift()!;
            promise = promise.then(resolved,rejected)
        }

        return promise;

        // return dispatchRequest(config)
    }

    get(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithoutData('get',url, config)
    }

    delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithoutData('delete',url, config)
    }

    head(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithoutData('head',url, config)
    }

    options(url: string, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithoutData('options',url, config)
    }

    post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithData('post', url, data, config)
    }

    put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithData('put', url, data, config)
    }

    patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
        return this._requestMethodWithData('patch', url, data, config)
    }

    
    // get、delete、head、options等无需传data的请求
    _requestMethodWithoutData(method: Method, url: string, config?: AxiosRequestConfig) {
        return this.request(
            Object.assign(config || {}, {
                method,
                url
            })
        )
    }
    
    // 处理含有data请求
    _requestMethodWithData(method: Method, url: string, data?: any, config?: AxiosRequestConfig) {
        return this.request(
            Object.assign(config || {}, {
              method,
              url,
              data
            })
          )
    }
}